/*
+--------------------------------------------------------------------------
|   mtons [#RELEASE_VERSION#]
|   ========================================
|   Copyright (c) 2014, 2015 mtons. All Rights Reserved
|   http://www.mtons.com
|
+---------------------------------------------------------------------------
 */
package com.hefei.blog.core.persist.base.impl;

import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;

import javax.annotation.PostConstruct;

import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.transform.ResultTransformer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;

import com.hefei.blog.base.paging.Paging;
import com.hefei.blog.core.persist.base.BaseRepository;
import com.hefei.blog.core.persist.base.annotation.Repository;

/**
 * 持久层基类, 通过泛型指定实体类
 * 
 * 基类中的 entityClass 在使用的时候必须呗赋值, 详情见 {@link mtons.modules.annotation.Repository}
 * 注解
 * 
 * @param <T>
 *            实体类
 * @author langhsu
 */
public class BaseRepositoryImpl<T> implements BaseRepository<T> {
	private static final long serialVersionUID = 3362415966471351147L;

	/**
	 * 使用的SessionFactory
	 */
	@Autowired
	protected SessionFactory sessionFactory;
	/**
	 * 实体类型
	 */
	protected Class<T> entityClass;

	public SessionFactory getSessionFactory() {
		return sessionFactory;
	}

	public void setSessionFactory(SessionFactory sessionFactory) {
		this.sessionFactory = sessionFactory;
	}

	 /**
     * 获得当前上下文Session
     * @return Session
     */
    protected Session session(){
        return this.sessionFactory.getCurrentSession();
    }
	/**
	 * 自动提取 entityClass
	 * @throws Exception出错
	 */
	@PostConstruct
	@SuppressWarnings("unchecked")
	public void init() throws Exception {
		Repository repository = this.getClass().getAnnotation(Repository.class);

		Assert.notNull(repository, this.getClass() + " 必须要使用" + Repository.class + "注解!");
		Assert.notNull(repository.entity(), this.getClass() + " 的 @Repository注解的 entity 不能为空!");

		this.entityClass = (Class<T>) repository.entity();
	}

	/**
     * 启用Filter
     * @param name filterName
     */
    protected void enableFilter(String name){
        session().enableFilter(name);
    }

    /**
     * 持久化实体对象
     * @param entity 实体对象
     */
    @Override
	public void save(Object entity) {
		Assert.notNull(entity, "entity不能为空"); 
		session().save(entity).hashCode();
	}

    /**
	 * 删除持久化对象
	 * 
	 * @param entity 实体对象
	 */
	@Override
	public void delete(Object entity) {
		Assert.notNull(entity, "entity不能为空");
		session().delete(entity);
	}

	/**
	 * 修改持久化对象
	 * 
	 * @param entity 实体对象
	 */
	@Override
	public void update(Object entity) {
		Assert.notNull(entity, "entity不能为空");
		session().update(entity);
	}

	/**
	 * 添加/修改
	 * 
	 * @param entity 实体对象
	 */
	@Override
	public void saveOrUpdate(Object entity) {
		Assert.notNull(entity, "entity不能为空");
		session().saveOrUpdate(entity);
	}
	
	/**
	 * refresh持久化对象
	 * 
	 * @param entity 实体对象
	 */
	@Override
	public void refresh(Object entity) {
		Assert.notNull(entity, "entity不能为空");
		session().refresh(entity);
	}
	
	/**
	 * merge
	 * 
	 * @param entity 实体对象
	 */
	@Override
	public Object merge(Object entity) {
		Assert.notNull(entity, "entity不能为空");
		return session().merge(entity);
	}
	
	/**
	 * get 查询
	 * 
	 * @param clazz 实体类
	 * @param <E> 实体类类型
	 * @param id 主键
	 * @return 实体对象
	 */
    @SuppressWarnings("unchecked")
	public <E> E get(Class<E> clazz, Serializable id) {
		return (E) session().get(clazz, id);
	}

    /**
	 * 根据主键删除
	 * 
	 * @param clazz 实体类
	 * @param id 主键
	 */
    protected void deleteById(Class<?> clazz, Serializable id) {
		Session s = session();
		Object obj = s.get(clazz, id);
		if (obj != null) {
			s.delete(obj);
		}
	}
    
    /** 
     * 根据查询HQL与参数列表创建Query对象. 
     * 与find()函数可进行更加灵活的操作. 
     * 
     * @param hql hql语句
     * @param values 数量可变的参数,按顺序绑定.
     * @return Query
     */  
    public Query createQuery(final String hql, final Object... values) {
    	Assert.hasText(hql, "hql不能为空");
    	Query query = session().createQuery(hql);
    	if (values != null) {
    		for (int i = 0; i < values.length; i++) {
    			query.setParameter(i, values[i]);
    		}
    	}
    	return query;
    }
    
    /**
     * 创建带缓存的HQL查询
     * @param hql hql语句
     * @return Query
     */
    protected Query createQuery(String hql){
    	Query q = session().createQuery(hql);
        return q;
    }

//    /**
//     * 创建HQL查询，通过传入参数设置是否可缓存
//     * @param hql hql语句
//     * @param cacheable 设置是否可缓存
//     * @return Query
//     */
//    protected Query createQuery(String hql, boolean cacheable){
//        return createQuery(hql, cacheable, null);
//    }

//    /**
//     * 创建HQL查询，通过传入参数设置是否可缓存
//     * @param hql hql语句
//     * @param cacheable 设置是否可缓存
//     * @param cacheRegion 缓存保存区域
//     * @return Query
//     */
//    protected Query createQuery(String hql, boolean cacheable, String cacheRegion){
//        Query q = session().createQuery(hql).setCacheable(true);
//        if (cacheable && cacheRegion != null) {
//            q.setCacheRegion(cacheRegion);
//        }
//        return q;
//    }
    
    /**
     * 创建Native SQL查询
     * @param sql sql语句
     * @return SQLQuery
     */
    protected SQLQuery createSQLQuery(String sql){
        return session().createSQLQuery(sql);
    }
    
    /**
     * 创建带缓存的QBC查询
     * @param clazz 查询的实体类型
     * @return Criteria
     */
    protected Criteria createCriteria(Class<?> clazz){
//        return session().createCriteria(clazz).setCacheable(true);
    	return session().createCriteria(clazz);
    }
    
    /**
     * 创建分页查询
     * @param paging 分页对象
     * @param clazz 返回实体对象类
     * @param <E> 实体类类型
     * @return PagingQuery
     */
    protected <E> PagingQuery<E> pagingQuery(Paging paging, Class<E> clazz){
        return new PagingQuery<E>(paging, clazz);
    }

//    /**
//     * 创建分页查询
//     * @param paging 分页对象
//     * @param clazz 返回实体对象类
//     * @param <E> 实体类类型
//     * @param cacheRegion 缓存保存区域
//     * @return PagingQuery
//     */
//    protected <E> PagingQuery<E> pagingQuery(Paging paging, Class<E> clazz, String cacheRegion){
//        return new PagingQuery<E>(paging, clazz, cacheRegion);
//    }

    
	/**
	 * 通过主键删除对象
	 * 
	 * @param id
	 *            主键
	 */
	@Override
	public void deleteById(Serializable id) {
		deleteById(entityClass, id);
	}

	/**
	 * 根据ID列表删除
	 * 
	 * @param ids
	 *            主键集合
	 */
	@Override
	public void deleteAll(Collection<Serializable> ids) {
		Assert.notNull(ids, "ids不能为空");
		ids.forEach(id -> deleteById(id));
	}

	/**
	 * 通过主键获取数据对象
	 * 
	 * @param id
	 *            主键
	 * @return 持久对象
	 */
	@Override
	public T get(Serializable id) {
		return get(entityClass, id);
	}

	/**
	 * 按Criteria查询对象列表.
	 * 
	 * @param criterions
	 *            数量可变的Criterion.
	 * @return 持久队列集合
	 */
	@SuppressWarnings("unchecked")
	protected List<T> find(final Criterion... criterions) {
		return createCriteria(criterions).list();
	}

	/**
	 * 按属性查找对象列表, 匹配方式为相等.
	 * 
	 * @param propertyName
	 *            键
	 * @param value
	 *            值
	 * @return 持久队列集合
	 */
	protected List<T> findBy(final String propertyName, final Object value) {
		Assert.hasText(propertyName, "propertyName不能为空");
		Criterion criterion = Restrictions.eq(propertyName, value);
		return find(criterion);
	}

	/**
	 * 按属性查找唯一对象, 匹配方式为相等.
	 * 
	 * @param propertyName
	 *            键
	 * @param value
	 *            值
	 * @return 实体对象
	 */
	@SuppressWarnings("unchecked")
	protected T findUniqueBy(final String propertyName, final Object value) {
		Assert.hasText(propertyName, "propertyName不能为空");
		Criterion criterion = Restrictions.eq(propertyName, value);
		return (T) createCriteria(criterion).uniqueResult();
	}

	/**
	 * 按HQL查询唯一对象.
	 * 
	 * @param hql
	 *            hql语句
	 * @param values
	 *            数量可变的参数,按顺序绑定.
	 * @return 实体对象
	 */
	@SuppressWarnings("unchecked")
	protected T findUnique(final String hql, final Object... values) {
		return (T) createQuery(hql, values).uniqueResult();
	}

	/**
	 * 按属性查询, 返回第一个对象
	 * 
	 * @param criterions
	 *            数量可变的Criterion.
	 * @return 结果集的第一个对象
	 */
	protected T findFirst(final Criterion... criterions) {
		List<T> rets = find(criterions);
		if (rets != null && rets.size() > 0) {
			return rets.get(0);
		}
		return null;
	}

	/**
	 * 按属性查询, 返回第一个对象
	 * 
	 * @param propertyName
	 *            键
	 * @param value
	 *            值
	 * @return 结果集的第一个对象
	 */
	protected T findFirst(final String propertyName, final Object value) {
		List<T> rets = findBy(propertyName, value);
		if (rets != null && rets.size() > 0) {
			return rets.get(0);
		}
		return null;
	}

	/**
	 * 按属性查询, 返回第一个对象
	 * 
	 * @param hql
	 *            hql语句
	 * @param values
	 *            数量可变的参数,按顺序绑定.
	 * @return 结果集的第一个对象
	 */
	@SuppressWarnings("unchecked")
	protected T findFirst(final String hql, final Object... values) {
		List<T> rets = createQuery(hql, values).list();
		if (rets != null && rets.size() > 0) {
			return rets.get(0);
		}
		return null;
	}

	/**
	 * 执行HQL进行批量修改/删除操作.
	 * 
	 * @param hql
	 *            hql语句
	 * @param values
	 *            数量可变的参数,按顺序绑定.
	 * @return 更新记录数.
	 */
	protected int batchExecute(final String hql, final Object... values) {
		return createQuery(hql, values).executeUpdate();
	}

	/**
	 * 创建Criteria查询对象
	 * 
	 * @return Criteria
	 */
	protected Criteria createCriteria() {
		return createCriteria(entityClass);
	}

	/**
	 * 根据Criterion条件创建Criteria. 与find()函数可进行更加灵活的操作.
	 * 
	 * @param criterions
	 *            数量可变的Criterion.
	 * @return Criteria
	 */
	protected Criteria createCriteria(final Criterion... criterions) {
		Criteria criteria = session().createCriteria(entityClass);
		for (Criterion c : criterions) {
			criteria.add(c);
		}
		return criteria;
	}

	/**
	 * 创建PagingQuery查询对象
	 * 
	 * @param paging
	 *            分页对象
	 * @return PagingQuery
	 */
	protected PagingQuery<T> pagingQuery(Paging paging) {
		return pagingQuery(paging, entityClass);
	}

	/**
	 * 创建PagingQuery查询对象
	 * 
	 * @param paging
	 *            分页对象
	 * @param criterions
	 *            数量可变的Criterion.
	 * @return PagingQuery
	 */
	protected PagingQuery<T> pagingQuery(Paging paging, Criterion... criterions) {
		PagingQuery<T> q = pagingQuery(paging, entityClass);

		for (Criterion c : criterions) {
			q.add(c);
		}
		return q;
	}
//
//	/**
//	 * 创建PagingQuery查询对象
//	 * 
//	 * @param paging
//	 *            分页对象
//	 * @param cacheRegion
//	 *            缓存保存区域
//	 * @return PagingQuery
//	 */
//	protected PagingQuery<T> pagingQuery(Paging paging, String cacheRegion) {
//		return pagingQuery(paging, entityClass, cacheRegion);
//	}

	/**
	 * 查询所有 (谨慎使用)
	 * 
	 * @return 集合
	 */
	@Override
	@SuppressWarnings("unchecked")
	public List<T> list() {
		return createCriteria().list();
	}

    protected class PagingQuery<T> implements Serializable {
		private static final long serialVersionUID = 8307596869106859651L;
        Class<T> clazz;
        
        QueryFilter filter = new QueryFilter();
        LinkedList<Order> orderBuffer = new LinkedList<Order>();
        ResultTransformer resultTransformer;
        Paging paging;

        public PagingQuery(Paging paging, Class<T> clazz){
        	this.clazz = clazz;
            this.paging = paging;
        }
        
        public PagingQuery<T> add(Order order) {
        	orderBuffer.add(order);
        	return this;
        }
        
        public PagingQuery<T> asc(String field){
        	add(Order.asc(field));
            return this;
        }

        public PagingQuery<T> desc(String field){
            add(Order.desc(field));
            return this;
        }
        
        public PagingQuery<T> alias(String field, String alias){
            filter.alias(field, alias);
            return this;
        }
        
        public PagingQuery<T> add(Criterion c){
        	filter.add(c);
            return this;
        }
        
        public PagingQuery<T> setResultTransformer(ResultTransformer resultTransformer) {
        	this.resultTransformer = resultTransformer;
        	return this;
        }
        
        protected Criteria criteria(boolean sortable){
            Criteria c = createCriteria(clazz);
            
            filter.doFilter(c);

            if (sortable && !orderBuffer.isEmpty()) {
            	orderBuffer.forEach(o -> c.addOrder(o));
            }
            return c;
        }
        
        /**
         * execute query
         * @return result
         */
        @SuppressWarnings("unchecked")
        public List<T> list(){
            boolean hasData = true;
            // count
            if (paging.isCount()) {
            	Criteria ctr = criteria(false);
                int totalRows = ((Number) ctr.setProjection(Projections.rowCount()).uniqueResult()).intValue();
                paging.setTotalCount(totalRows);
                if (totalRows <= paging.getFirstResult()) {
                	hasData = false;
                }
            }
            
            // query data
            List<T> results = Collections.emptyList();
            if (hasData) {
                Criteria ctr = criteria(true);
                ctr.setFirstResult(paging.getFirstResult()).setMaxResults(paging.getMaxResults());
                
                if (resultTransformer != null) {
                	ctr.setResultTransformer(resultTransformer);
                }
                results = ctr.list();
            }
            paging.setResults(results);
            return results;
        }
    }
    
    /**
     * PagingQuery filter
     */
    protected class QueryFilter {
    	LinkedList<Criterion> criterions = new LinkedList<Criterion>();
    	LinkedHashMap<String, String> aliases = new LinkedHashMap<String, String>();
    	
    	public QueryFilter add(Criterion c) {
    		criterions.add(c);
    		return this;
    	}
    	
    	public QueryFilter alias(String field, String alias){
            aliases.put(field, alias);
            return this;
        }
    	
        public void doFilter(Criteria criteria) {
        	if (!criterions.isEmpty()) {
                if (!aliases.isEmpty()) {
                	
                	aliases.forEach((k, v) -> criteria.createAlias(k, v));
                }
                criterions.forEach(ct -> criteria.add(ct));
            }
        }
    }
}
